
<template>
  <div style="width:1100px;height:250px;">
    <svg ref="ganttChart" width="1100" height="100%">
      <!-- 定义渐变 -->
      <defs>
        <linearGradient id="taskGradient" x1="0%" y1="0%" x2="100%" y2="0%">
          <stop offset="0%" style="stop-color:#EB5095;stop-opacity:1" />
          <stop offset="100%" style="stop-color:#FFB9D8;stop-opacity:1" />
        </linearGradient>
      </defs>
    </svg>
  </div>
</template>

<script>
import moment from 'moment'
export default {
  name: 'GanttChart',
  data() {
    return {
      cellWidth: 50,
      cellHeight: 40,
      taskHeight: 16,
      tasks: [
        {
          name: '任务 1',
          periods: [
            { start: new Date('2025-01-02'), end: new Date('2025-01-04') },
            { start: new Date('2025-01-06'), end: new Date('2025-01-08') }
          ],
          events: [
            { type: '开始', time: new Date('2025-01-02') },
            { type: '暂停', time: new Date('2025-01-04') },
            { type: '开始', time: new Date('2025-01-06') },
            { type: '结束', time: new Date('2025-01-08') }
          ]
        },
        {
          name: '任务 2',
          periods: [
            { start: new Date('2025-01-03'), end: new Date('2025-01-05') },
            { start: new Date('2025-01-07'), end: new Date('2025-01-09') }
          ],
          events: [
            { type: '开始', time: new Date('2025-01-03') },
            { type: '暂停', time: new Date('2025-01-05') },
            { type: '开始', time: new Date('2025-01-07') },
            { type: '结束', time: new Date('2025-01-09') }
          ]
        }
      ],
      // 起始日期
      startTime: new Date('2025-01-01'),
      // 结束日期
      endTime: new Date('2025-01-02'),

    };
  },
  mounted() {
    this.drawGanttChart();
  },
  methods: {
    drawGanttChart() {
      const svg = this.$refs.ganttChart;
      const { startTime, endTime, cellWidth, cellHeight, taskHeight } = this;
      const timeSpan = (endTime - startTime) / (1000 * 60 * 60 );
      const width = timeSpan * cellWidth;
      const height = this.tasks.length * cellHeight;

      this.drawGrid(svg, timeSpan, width, height, cellWidth, cellHeight);
      this.drawAxes(svg, timeSpan, width, height, cellWidth, cellHeight, startTime);
      this.drawTasks(svg, cellWidth, cellHeight, taskHeight, startTime);
      this.drawEvents(svg, cellWidth, cellHeight, taskHeight, startTime);
    },
    drawGrid(svg, timeSpan, width, height, cellWidth, cellHeight) {
      // 绘制垂直格子线
      for (let i = 0; i <= timeSpan; i++) {
        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line.setAttribute('x1', i * cellWidth);
        line.setAttribute('y1', 0);
        line.setAttribute('x2', i * cellWidth);
        line.setAttribute('y2', height);
        line.setAttribute('stroke', '#ccc');
        svg.appendChild(line);
      }

      // 绘制水平格子线
      for (let i = 0; i <= this.tasks.length; i++) {
        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
        line.setAttribute('x1', 0);
        line.setAttribute('y1', i * cellHeight);
        line.setAttribute('x2', width);
        line.setAttribute('y2', i * cellHeight);
        line.setAttribute('stroke', '#ccc');
        svg.appendChild(line);
      }
    },
    drawAxes(svg, timeSpan, width, height, cellWidth, cellHeight, startTime) {
      // 绘制横坐标
      for (let i = 0; i <= timeSpan; i++) {
        const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
        const currentDate = new Date(startTime);
        currentDate.setDate(currentDate.getDate() + i);
        const dateString = moment(currentDate).format('HH');
        text.setAttribute('x', i * cellWidth + 5);
        text.setAttribute('y', height + 15);
        text.textContent = dateString;
        text.setAttribute('font-size', '12px');
        text.setAttribute('visibility', 'visible');
        svg.appendChild(text);
      }

      // 绘制纵坐标
      this.tasks.forEach((task, index) => {
        const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
        text.setAttribute('x', -20);
        text.setAttribute('y', index * cellHeight + 20);
        text.textContent = task.name;
        text.setAttribute('font-size', '12px');
        text.setAttribute('visibility', 'hidden');
        svg.appendChild(text);
      });
    },
    drawTasks(svg, cellWidth, cellHeight, taskHeight, startTime) {
      // 绘制任务时间段
      this.tasks.forEach((task, taskIndex) => {
        task.periods.forEach(period => {
          const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
          const startOffset = (period.start - startTime) / (1000 * 60 * 60 * 24);
          const endOffset = (period.end - startTime) / (1000 * 60 * 60 * 24);
          const x = startOffset * cellWidth;
          const y = taskIndex * cellHeight + (cellHeight - taskHeight) / 2;
          const rectWidth = (endOffset - startOffset) * cellWidth;
          rect.setAttribute('x', x);
          rect.setAttribute('y', y);
          rect.setAttribute('width', rectWidth);
          rect.setAttribute('height', taskHeight);
          rect.setAttribute('fill', 'url(#taskGradient)');
          rect.setAttribute('stroke', '#ccc');
          rect.setAttribute('stroke-width', '1');
          svg.appendChild(rect);
        });
      });
    },
    drawEvents(svg, cellWidth, cellHeight, taskHeight, startTime) {
      const iconSvg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_sa28_26842"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_sa28_26842)"><g><path d="M7.619995150146484,10.07000036819458C7.828115150146484,10.28405036819458,8.171885150146483,10.28405036819458,8.379995150146485,10.07000036819458L11.220035150146485,7.22000036819458C11.393735150146485,7.07598036819458,11.443635150146484,6.83043036819458,11.340035150146484,6.6300003681945805C11.262335150146484,6.42339036819458,11.060535150146485,6.29020036819458,10.840005150146485,6.30000036819458L9.610005150146485,6.30000036819458L9.610005150146485,2.54000036819458C9.611305150146485,2.3966973681945802,9.553415150146485,2.2592083681945803,9.450005150146485,2.15999936819458C9.351495150146484,2.05555916819458,9.213545150146484,1.9974714481945801,9.069995150146484,1.9999999999945801L6.919995150146485,1.9999999999945801C6.776695150146485,1.9986919181945801,6.639205150146484,2.05658246819458,6.539995150146485,2.16000036819458C6.4355551501464845,2.25850136819458,6.377475150146484,2.39645936819458,6.379995150146485,2.54000036819458L6.379995150146485,6.31000036819458L5.159995150146484,6.31000036819458C4.938585150146484,6.29693036819458,4.735025150146484,6.43128036819458,4.659995150146484,6.64000036819458C4.560205150146484,6.84095036819458,4.609645150146484,7.08400036819458,4.779995150146485,7.23000036819458L7.619995150146484,10.07000036819458ZM14.770035150146484,9.93000036819458C14.620135150146485,9.777540368194579,14.413735150146485,9.69422036819458,14.200035150146485,9.70000036819458L10.289995150146485,9.70000036819458L9.140005150146484,10.84000036819458C8.505215150146483,11.46651036819458,7.484785150146484,11.46651036819458,6.850005150146484,10.84000036819458L5.720005150146484,9.69000036819458L1.8100001501464844,9.69000036819458C1.3603271501464844,9.68434036819458,0.9943434001464844,10.05033036819458,1.0000000000464844,10.50000036819458L1.0000000000464844,13.19002036819458C0.9943432801464843,13.63972036819458,1.3603271501464844,14.00562036819458,1.8100001501464844,14.00002036819458L14.190035150146484,14.00002036819458C14.639635150146484,14.00562036819458,15.005635150146484,13.63972036819458,15.000035150146484,13.19002036819458L15.000035150146484,10.50000036819458C15.002335150146484,10.28695036819458,14.919535150146485,10.08179036819458,14.770035150146484,9.93000036819458Z" fill="#999999" fill-opacity="1"/></g></g></svg>`;
      this.tasks.forEach((task, taskIndex) => {
        task.events.forEach(event => {
          const timeOffset = (event.time - startTime) / (1000 * 60 * 60 * 24);
          const x = timeOffset * cellWidth;
          const y = taskIndex * cellHeight + cellHeight / 2 - 8; // 减去图标高度的一半以居中

          const parser = new DOMParser();
          const iconDoc = parser.parseFromString(iconSvg, 'image/svg+xml');
          const iconElement = iconDoc.documentElement;
          iconElement.setAttribute('x', x);
          iconElement.setAttribute('y', y);
          svg.appendChild(iconElement);
        });
      });
    }
  }
};
</script>

<style scoped>
svg {
  border: 1px solid #ccc;
}
</style>
